/*
"The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is ReportSheet.

The Initial Developer of the Original Code is Amarnath Chatterjee.
Portions created by Amarnath Chatterjee are Copyright (C). All Rights Reserved.

Contributor(s): Amarnath Chatterjee.
*/

package Reporting
{
	import mx.controls.Alert;
	import Reporting.Utils.CommonUtils;
	//This class is responsible for managing dependencies which arise as a part of math equations.
	//Some pointers to keep in mind, while developing this class further.
	//1. In case of error this class must restore its last stable state.
	//2. The class contains some recursive implementations.
	
	public class MathCellDependency
	{
		//Restore checkpoint variables ------------------
		private var m_restoreAdjMat : Array = null;
		private var m_restoreOrder : Array = null;
		//-----------------------------------------------

		private var m_adjecencyMatrix : Array = new Array();
		private var m_nodes : int = 0;
		
		//This variable is used to track the max cols to get the ith node using r*maxc + c.
		//each cell is uniquely identified using r and c.
		private var m_maxcol : int = 0;
		
		//Topological Order
		private var m_order : Array;
		private var m_topoCtr : int = 0;

		//Cycle tracker
		private var m_visit : Array;
		private var m_visitCtr : int;
		
		//Current operators to be parsed.
		private var m_operators : RegExp = EquationSolver.OPERATORS;
		private var m_CellRefOperators : RegExp = EquationSolver.CELL_REF_OPERATORS;

		public function MathCellDependency(maxr : int, maxc: int)
		{
			m_nodes = maxc*maxr;
			m_maxcol = maxc;
			for ( var i : int = 0; i < m_nodes; i++)
			{
				m_adjecencyMatrix[i] = new Array();
				for ( var j : int = 0; j < m_nodes; j++)
				{
					m_adjecencyMatrix[i][j] = false;
				}
			}
		}
		
		public function AddDependant( parent : CellInfo, child : CellInfo) : void
		{
			var parentCell : int = parent.row * m_maxcol + parent.col;
			var childCell : int = child.row * m_maxcol + child.col;
			
			m_adjecencyMatrix[parentCell][childCell] = true;
		}
		
		public function RemDependant( parent : CellInfo, child : CellInfo) : void
		{
			var parentCell : int = parent.row * m_maxcol + parent.col;
			var childCell : int = child.row * m_maxcol + child.col;
			
			m_adjecencyMatrix[parentCell][childCell] = false;
		}
		
		//For a given parent it removes all dependents. Specially helpfull in a case when the
		//data from a cell changes from mathematicalequation to normal data.
		public function RemoveAllDependents(parent : CellInfo) : void
		{
			var parentCell : int = parent.row * m_maxcol + parent.col;
			for ( var j : int = 0 ; j < m_nodes; j++)
			{
				m_adjecencyMatrix[parentCell][j] = false;
			}
			CalculateDependency();
		} 
		
		//Calculate topological order for all cells.
		//In case of failure, it will keep restore to old values.
		public function CalculateDependency() : void
		{
			//take backup
			m_restoreOrder = m_order;
			
			m_topoCtr = 0;
			m_visitCtr = 0;
			m_order = new Array();
			m_visit = new Array();

			
			for( var i : int = 0; i < m_nodes; i++)
			{
				m_visit[i] = -1;
				m_order[i] = -1;
			}

			try
			{
				for ( var j : int = 0; j < m_nodes; j++)
				{
					if(m_visit[j] == -1)
						DFS(j);
				}
			}
			catch(e : Error)
			{
				//restore
				m_order = m_restoreOrder;
				//rethrow
				throw e;
			}
		}
		
		//This function gives out a copy of topological ordering.
		public function GetCompleteOrder() : Array
		{
			var retOrder : Array = new Array();
			for( var i : int = 0; i < m_order.length; i++)
			{
				retOrder[i] = m_order[i];
			}
			return retOrder;
		}

		//Helper function to find out topological order, uses depth-first search.
		private function DFS(curNode : int) : void
		{
			
			m_visit[curNode] = m_visitCtr++;
				
			for( var i : int = 0; i < m_nodes; i++)
			{
				if(m_adjecencyMatrix[curNode][i] == true)
				{
					//If not already visited
					if(m_visit[i] == -1)
						DFS(i);
					else if(m_order[i] == -1)
						throw new Error("Cycle detected");
				}
			}
			m_order[curNode] = m_topoCtr++;
		}

		//This function returns the topological order for the cells which will be changed as a part
		//of change in cell "changedCell"
		public function GetCellsToUpdate(changedCell : CellInfo) : Array
		{
			if(m_order == null || m_order.length == 0)
				return null;
		
			var CellIndex : int = changedCell.row * m_maxcol + changedCell.col;
			
			m_visit = new Array();
			m_visitCtr = 0;
			for( var lp : int = 0; lp < m_nodes; lp++)
			{
				m_visit[lp] = -1;
			}
			
			
		    RecursivelyTrackParents(CellIndex);
			//Once this call is finished. m_visit will contain all affected parents.
			//Next is to find out their orders.

			var updtArray : Array = new Array();
			
			//for each of the elements which is going to be updated.
			for( var i : int = 0, ctr : int = 0; i < m_visit.length; i++)
			{
				//if visited
				if( m_visit[i] != -1)
				{
					var minOrder : int = m_order[i];
					var minIndex : int = i;
					for( var j : int = 0; j < m_visit.length; j++)
					{
						//if visited
						if(m_visit[j] != -1)
						{
							var nodeOrder : int = m_order[j];
							if(nodeOrder < minOrder)
							{
								minOrder = nodeOrder;
								minIndex = j;
							}							
						}
					}
					var updtCell : CellInfo = new CellInfo(); 
					updtCell.row = minIndex/m_maxcol;
					updtCell.col = minIndex%m_maxcol;
					updtArray[ctr++] = updtCell;
					
					//exhausting the visited cell.
					m_visit[minIndex] = -1;
					
					//this is not a good idea. Have to revamp it, in future.
					i = -1;					
				}
				//if no more cells are to be updated, it will break off then. 
			}
			
			return updtArray;


		}
		
		//This function finds out all direct or indirect parents of the current cell.
		private function RecursivelyTrackParents( curNode : int  ) : void
		{
			m_visit[curNode] = m_visitCtr++;

			for ( var i : int = 0 ; i < m_nodes; i++)
			{
				if(m_adjecencyMatrix[i][curNode] == true )
				{
					RecursivelyTrackParents(i);
				}
			}
		}
		
		//This function parses the expression and sets dependency. Finally it calculates the order.
		public function ParseAndSetDependency( exprn : String, curCell : CellInfo ) : void
		{
			//First of all, remove all current dependencies. In case of Cyclic error , we have to restore
			//the original connections as well.
			var cellInd : int = curCell.row * m_maxcol + curCell.col;
			var oldConnectedNode : Array = m_adjecencyMatrix[cellInd];
			m_adjecencyMatrix[cellInd] = new Array();
			
			for ( var j : int = 0 ; j < m_nodes; j++)
			{
				m_adjecencyMatrix[cellInd][j] = false;
			}
			var exprnTokens : Array = exprn.split(m_operators);
			for( var i : int = 0; i < exprnTokens.length; i++)
			{				
				var cellrefTokens : Array = exprnTokens[i].split(m_CellRefOperators);
				if(cellrefTokens.length == 4)
				{
					var chCell : CellInfo = new CellInfo();
					
					chCell.row = parseInt(cellrefTokens[1]);
					chCell.col = parseInt(cellrefTokens[2]);
					AddDependant(curCell, chCell);					
				}
			}
			
			//For restoring calculations if the new connection bring cyclic dependencies.
			try
			{
				//While calculating dependency, if this cell finds some error, it throws error, but
				//restores to its previous state.
				CalculateDependency();
			}
			catch(er : Error)
			{
				//restore connections. 
				m_adjecencyMatrix[cellInd] = oldConnectedNode;
				
				//Ahead also we have to tell about the error.
				throw er;
			}
				
		}
		
	}
}
